/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.database;

import com.zaxxer.hikari.HikariDataSource;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.data.source.DataSourceIdentifierResolver;
import io.iec.edp.caf.data.source.ConnectionProvider;
import io.iec.edp.caf.tenancy.core.extensions.TenantDataSourceProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RestController;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Database Management
 * 当前的链接释放只是放回连接池，没有断开物理链接，所以对于会话级的临时表会出现第二次调用时表已存在的情况
 * <p>
 *
 * @author guowenchang
 * @date 2020-11-03
 */
@Deprecated
@Slf4j
public class DatabaseManager {

    private final ThreadLocal<ConcurrentHashMap<String, Database>> databaseThreadLocal = new ThreadLocal<>();

    private boolean initialized = false;

    private ConnectionProvider connectionProvider;
    private DataSourceIdentifierResolver identifierResolver;

    /**
     * 饿汉式实现单例
     */
    private static final DatabaseManager instance = new DatabaseManager();

    private DatabaseManager() {
    }

    /**
     * 获取实例对象
     *
     * @return
     */
    public static DatabaseManager getInstance() {
        return instance;
    }

    public void ensureInitialized() {
        if (!initialized) {
            connectionProvider = (ConnectionProvider) SpringBeanUtils.getBean(ConnectionProvider.class);
            identifierResolver = (DataSourceIdentifierResolver) SpringBeanUtils.getBean(DataSourceIdentifierResolver.class);
            databaseThreadLocal.set(new ConcurrentHashMap<>());
            initialized = true;
        }
    }

    /**
     * 开启上下文环境
     */
    public void begin() throws SQLException {
        ensureInitialized();


        synchronized (databaseThreadLocal) {
            String databaseId = identifierResolver.resolveDataSourceIdentifier();
            Database database = getCurrentDatabase(databaseId);

            //上下文中不存在 则组建新的Database对象
            if (database == null) {
                Connection connection = connectionProvider.getConnection(databaseId);
                if(log.isInfoEnabled()) log.info("\n线程"+Thread.currentThread().getId()+"正在调用begin方法,上下文不存在Database，组建新的Database对象\n");
                database = new Database(connection,databaseId);
                setCurrentDatabase(databaseId, database);

            }


            if(log.isInfoEnabled()){
                Throwable ex = new Throwable();
                log.info("\n线程"+Thread.currentThread().getId()+"正在调用begin方法,部分堆栈信息：\n",ex);
                log.info("\n线程"+Thread.currentThread().getId()+"当前databaseId"+databaseId+"已成功建立JDBC连接"+
                        "\n线程"+Thread.currentThread().getId()+"当前连接信息："+database.getJdbcTemplate().getDataSource().getConnection().toString());
            }

        }
    }

    /**
     * 结束上下文环境，并关闭物理连接
     */
    public void endCon(){
        ensureInitialized();

        synchronized (databaseThreadLocal) {
            String databaseId = identifierResolver.resolveDataSourceIdentifier();
            Database database = getCurrentDatabase(databaseId);

            if (database == null) {
                if(log.isInfoEnabled()) log.info("\n线程"+Thread.currentThread().getId()+"正在调用end方法: Database context is already closed, databaseId:"+databaseId);
                throw new RuntimeException("Database context is already closed.");
            }else{
                database.destroyConnection();
            }
            clearCurrentDatabase(databaseId);
        }
    }

    /**
     * 结束上下文环境
     */
    public void end() {
        ensureInitialized();

        synchronized (databaseThreadLocal) {
            String databaseId = identifierResolver.resolveDataSourceIdentifier();
            Database database = getCurrentDatabase(databaseId);

            if(log.isInfoEnabled()){
                Throwable ex = new Throwable();
                log.info("\n线程"+Thread.currentThread().getId()+"正在调用end方法,部分堆栈信息: "+ex);
            }
            //上下文中不存在 则组建新的Database对象
            if (database == null) {

                if(log.isInfoEnabled()) log.info("\n线程"+Thread.currentThread().getId()+"正在调用end方法: Database context is already closed, databaseId:"+databaseId);
                throw new RuntimeException("Database context is already closed.");
            }else{
                database.release();
                //database.evictConnection();
                //database.destroyConnection();
            }

            clearCurrentDatabase(databaseId);
        }
    }

    /**
     * 是否在上下文环境中
     *
     * @return
     */
    public Boolean inContext() {
        ensureInitialized();
        String databaseId = identifierResolver.resolveDataSourceIdentifier();
        Database database = getCurrentDatabase(databaseId);

        return database != null;
//      去除同步锁，提高性能
//        synchronized (databaseThreadLocal) {
//            String databaseId = identifierResolver.resolveDataSourceIdentifier();
//            Database database = getCurrentDatabase(databaseId);
//
//            return database != null;
//        }
    }

    /**
     * 获取Database对象
     * 如果上下文中有暂存的Database 则取此连接 如果没有 则新建连接
     *
     * @return
     */
    public Database getDatabase() throws SQLException {
        ensureInitialized();

        synchronized (databaseThreadLocal) {
            String databaseId = identifierResolver.resolveDataSourceIdentifier();
            Database database = getCurrentDatabase(databaseId);
//            //上下文中存在 则返回当前的 否则创建新的
//            if (database == null) {
//                Connection connection = connectionProvider.getConnection(databaseId);
//                database = new Database(connection);
//            }
            log.info("\n线程"+Thread.currentThread().getId()+"获取Database对象，databaseId："+databaseId);
            return database;
        }
    }

    /**
     * 归还Database对象
     * <p>
     * 上下文中暂存的Database 调用此方法时不归还
     *
     * @param db
     */
    public void requiteDatabase(Database db) {
        ensureInitialized();

        synchronized (databaseThreadLocal) {
            String databaseId = identifierResolver.resolveDataSourceIdentifier();
            Database database = getCurrentDatabase(databaseId);

            //上下文中存在 则不归还 直接返回
            //上下文中不存在才归还
            if (database == null) {
                db.release();
            }
        }
    }

    /**
     * 获取当前上下文中的database
     *
     * @param databaseId
     * @return
     */
    private Database getCurrentDatabase(String databaseId) {
        ConcurrentHashMap<String, Database> dbMap = this.databaseThreadLocal.get();
        if (dbMap == null) {
            dbMap = new ConcurrentHashMap<>();
            this.databaseThreadLocal.set(dbMap);
        }

        if(dbMap.get(databaseId)==null)
            log.info("\n线程"+Thread.currentThread().getId()+"上下文没有caf-database，将创建新的数据库连接。");
        else
            log.info("\n线程"+Thread.currentThread().getId()+"获取上下文的caf-database，databaseId："+databaseId);
        return dbMap.get(databaseId);
    }

    /**
     * 设置当前上下文中的database
     *
     * @param databaseId
     * @param database
     */
    private void setCurrentDatabase(String databaseId, Database database) {
        ConcurrentHashMap<String, Database> dbMap = this.databaseThreadLocal.get();
        if (dbMap == null) {
            dbMap = new ConcurrentHashMap<>();
            this.databaseThreadLocal.set(dbMap);
        }
        log.info("\n线程"+Thread.currentThread().getId()+"设置当前上下文的database,databaseId:"+databaseId);
        dbMap.put(databaseId, database);
    }

    /**
     * 清除当前上下文中的database
     *
     * @param databaseId
     */
    private void clearCurrentDatabase(String databaseId) {
        ConcurrentHashMap<String, Database> dbMap = this.databaseThreadLocal.get();
        if (dbMap == null) {
            dbMap = new ConcurrentHashMap<>();
            this.databaseThreadLocal.set(dbMap);
        }

        dbMap.remove(databaseId);
        log.info("\n线程"+Thread.currentThread().getId()+"清除当前上下文中的database,databaseId:"+databaseId);

    }
}
